Los datos que se van a analizar en este proyecto han sido obtenidos desde Kaggle. Contienen precios de casas que fueron vendidas desde mayo de 2014 hasta mayo de 2015 en King County que es un condado ubicado en el estado estadounidense de Washington.
Lo que queremos hacer con estos datos es clasificar/predecir las viviendas conforme a su precio dependiendo de las variables recogidas de cada una. Para ello se implementarán varios modelos, comprobando cuáles de ellos funciona mejor con los tipos de variables que contamos. Finalmente se evaluarán los modelos en base a distintas métricas.
En nuestro estudio inicial, la variable “Precio” es continua, por lo que vamos a categorizarla. Para decidir las categorizaciones se ha usado los cuantiles. Se van a realizar dos tipos de categorizaciones:
Categorización 1: se ha categorizado en dos grupos:
C1: casas caras (casas con un precio > 500.000).
Categorización 2: se ha categorizado en tres grupos:
#Categorizamos la variable respuesta price:
quantile(datos$price, prob=seq(0, 1, length = 5))
## 0% 25% 50% 75% 100%
## 75000 321950 450000 645000 7700000
datos$price_categ1 <- cut(datos$price, breaks = c(0, 500000, 100000000), labels = c("B1", "C1"))
table(datos$price_categ1)
##
## B1 C1
## 12560 9053
datos$price_categ2 <- cut(datos$price, breaks = c(0, 330000, 650000, 100000000), labels = c("B2","M2", "C2"))
table(datos$price_categ2)
##
## B2 M2 C2
## 5881 10525 5207
En el siguiente mapa se puede visualizar cómo se distribuyen las casas “Caras” y “Baratas”. Se observa que las casas que están cercanas al agua y cerca de Seattle por la parte norte son más caras y hacia el sur son más baratas.
center_lon = median(datos$long,na.rm = TRUE)
center_lat = median(datos$lat,na.rm = TRUE)
factpal2 <- colorFactor(c("green","red"),
datos$price_categ1 )
leaflet(datos) %>% addProviderTiles("Esri.NatGeoWorldMap") %>%
addCircles(lng = ~long, lat = ~lat,
color = ~factpal2(datos$price_categ1)) %>%
# controls
setView(lng=center_lon, lat=center_lat, zoom = 9) %>%
addLegend("bottomright", pal = factpal2 , values = ~datos$price_categ1,
title = "Tipos de Casas",
opacity = 1)
En este otro mapa se puede visualizar cómo se distribuyen las casas según el precio en tres categorías:“Caras”, “Medio” y “Baratas”. Se puede observar cómo con esta categorización no está tan clara la separación entre clases.
center_lon = median(datos$long,na.rm = TRUE)
center_lat = median(datos$lat,na.rm = TRUE)
factpal2 <- colorFactor(c("green","red","yellow"),
datos$price_categ2 )
leaflet(datos) %>% addProviderTiles("Esri.NatGeoWorldMap") %>%
addCircles(lng = ~long, lat = ~lat,
color = ~factpal2(datos$price_categ2)) %>%
# controls
setView(lng=center_lon, lat=center_lat,zoom = 9) %>%
addLegend("bottomright", pal = factpal2 , values = ~datos$price_categ2,
title = "Tipos de Casas 3 categorías",
opacity = 1)
Por lo tanto, viendo ambos mapas, nos quedamos con la primera categorización: casas baratas y caras.
Eliminamos la segunda categorización:
datos$price_categ2= NULL
Se va a separar los datos en los 3 conjuntos de datos fundamentales:
num_total=nrow(datos)
set.seed(122556) #reproductividad
# 70% para train
indices_train = sample(1:num_total, .7*num_total)
datos_train = datos[indices_train,]
# 15% para test
indices=seq(1:num_total)
indices_test=indices[-indices_train]
indices_test1 = sample(indices_test, .15*num_total)
datos_test = datos[indices_test1,]
# 15% para validacion
indices_validacion=indices[c(-indices_train,-indices_test1)]
datos_validacion=datos[indices_validacion,]
Se van a realizar transformaciones de un conjunto de variables, estas transformaciones se aplicarán a cada conjunto de datos: train, test y validación.
Se realiza una transformación logarítmica sobre las variables sqft_living (pies cuadrados de la casa), sqft_lot (pies cuadrados del jardín) y sqft_above (pies cuadrados por encima del suelo). Hay que aclarar que esta última variable es la diferencia entre sqft_living y sqft_basement, por lo que va a estar altamentente correlada con sqft_living.
Se categorizan las variables:
Bathroom, esta varible puede tomar valores decimales de 0.25 en 0.25. El número de baños se contabiliza por las piezas y cada baño completo tiene 4 piezas. Por lo que con la nueva agrupación toma valores de 1 a 8 baños.
Sqft_basement, se categoriza como 0 las casas que no tienen sótano y 1 las casas que sí tienen sótano.
Grade, se va a categorizar del siguiente modo: con valor 0: calidad Baja, 1: calidad media y 2: calidad alta.
Year_renovated, se categoriza como 0: no ha tenido renovación y 1: sí ha tenido renovación.
Se pasan a factor las variables: waterfront, view, condition, grade_categ y zipcode.
Se eliminan Outliers.
Se realizan las transformaciones anteriormente mencionada y se eliminan outliers:
datos_train <- datos_train[,-2]
datos_train$id <- as.factor(datos_train$id)
datos_train$bathrooms_group <- cut(datos_train$bathrooms,breaks = c(-1,0.25,1,2,3,4,5,6,7,8),labels=c(0,1,2,3,4,5,6,7,8))
datos_train$bathrooms_group <- as.numeric(as.character(datos_train$bathrooms_group))
datos_train$log_sqft_living <- log10(datos_train$sqft_living)
datos_train$log_lot <- log10(datos_train$sqft_lot)
datos_train$log_above <- log10(datos_train$sqft_above)
datos_train$sqft_basement_cat <- cut(datos_train$sqft_basement,breaks = c(-1,0,6000),labels=c(0,1))
datos_train$waterfront<-as.factor(datos_train$waterfront)
datos_train$view<-as.factor(datos_train$view)
datos_train$condition<-as.factor(datos_train$condition)
datos_train$grade_categ <- cut(datos_train$grade, breaks = c(0,4,9,13), labels = c(0,1,2))
datos_train$yr_renovated_catg <-cut(datos_train$yr_renovated, breaks=c(-0.5,1933, 2015), labels= c("0","1"))
datos_train$zipcode<-as.factor(datos_train$zipcode)
# Eliminación de Outliers
datos_train$posicion<-c(1:nrow(datos_train))
indices_cero_habitaciones<-datos_train[datos_train$bedrooms==0,]$posicion
datos_train<-datos_train[-indices_cero_habitaciones,]
datos_train$posicion<-c(1:nrow(datos_train))
indices_cero_banos<-datos_train$posicion[datos_train$bathrooms_group==0]
datos_train<-datos_train[-indices_cero_banos,]
datos_train$posicion<-c(1:nrow(datos_train))
indice_hab33 <- datos_train[datos_train$bedrooms==33,]$posicion
datos_train[datos_train$posicion == indice_hab33,]$bedrooms = 3
Se han realizado dos categorizaciones adicionales sobre el conjunto de datos. Después de implementar varios modelos, se llegó a la conclusión de que algunas variables podían mejorar los resultados de los modelos siendo agrupadas. Para analizar cómo recategorizar estas variables se ha usado un árbol de decisión. Las variables son: zipcode y bathrooms_group.
model_selec_zipcode<-rpart(price_categ1~zipcode,data=datos_train ,parms=list(split="gini"))
print(model_selec_zipcode)
## n= 15119
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 15119 6385 B1 (0.5776837 0.4223163)
## 2) zipcode=98001,98002,98003,98010,98011,98014,98019,98022,98023,98024,98028,98030,98031,98032,98034,98038,98042,98045,98055,98056,98058,98059,98070,98092,98106,98108,98118,98125,98126,98133,98144,98146,98148,98155,98166,98168,98178,98188,98198 8243 1336 B1 (0.8379231 0.1620769) *
## 3) zipcode=98004,98005,98006,98007,98008,98027,98029,98033,98039,98040,98052,98053,98065,98072,98074,98075,98077,98102,98103,98105,98107,98109,98112,98115,98116,98117,98119,98122,98136,98177,98199 6876 1827 C1 (0.2657068 0.7342932) *
Se va a categorizar en dos: Zona1 y Zona2.
datos_train$zona<-recode(datos_train$zipcode, "98001=1; 98002=1; 98003=1; 98010=1; 98011=1; 98014=1; 98019=1; 98022=1; 98023=1; 98024=1; 98028=1; 98030=1; 98031=1; 98032=1; 98034=1; 98038=1; 98042=1; 98045=1; 98055=1; 98056=1; 98058=1; 98059=1; 98070=1; 98092=1 ;98106=1; 98108=1; 98118=1; 98125=1; 98126=1; 98133=1; 98144=1; 98146=1; 98148=1; 98155=1; 98166=1; 98168=1; 98178=1; 98188=1; 98198=1; 98004=2; 98005=2; 98006=2; 98007=2; 98008=2; 98027=2; 98029=2; 98033=2; 98039=2; 98040=2; 98052=2; 98053=2; 98065=2; 98072=2; 98074=2; 98075=2; 98077=2; 98102=2; 98103=2; 98105=2; 98107=2; 98109=2; 98112=2; 98115=2; 98116=2; 98117=2; 98119=2; 98122=2; 98136=2; 98177=2; 98199=2")
datos_train$zipcode = NULL
model_selec_bathrooms<-rpart(price_categ1~bathrooms_group,data=datos_train ,parms=list(split="gini"))
print(model_selec_bathrooms)
## n= 15119
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 15119 6385 B1 (0.5776837 0.4223163)
## 2) bathrooms_group< 2.5 7282 1850 B1 (0.7459489 0.2540511) *
## 3) bathrooms_group>=2.5 7837 3302 C1 (0.4213347 0.5786653) *
En el resultado del modelo se ve que corta en el número de baños < 2.5, por lo que se va a categorizar como 0 aquellas casas que tengan de 1 a 2 baños y como 1 las casas que tengan más de 2 baños.
datos_train$bathrooms_group <- cut(datos_train$bathrooms_group, breaks = c(-1,2.5,8),labels=c(0,1))
# Limpiamos el dataframe:
datos_train_limpio <- datos_train[c(3,21:25,8:10,26,16,17,29,27,20)]
#Eliminamos sqft_above:
datos_train_limpio$log_above = NULL
datos_train_numeric <- datos_train_limpio %>% select_if(is.numeric)
Realizamos todas las transformaciones y categorizaciones para el conjunto de datos de test.
datos_test <- datos_test[,-2]
datos_test$id <- as.factor(datos_test$id)
datos_test$bathrooms_group <- cut(datos_test$bathrooms,breaks = c(-1,0.25,1,2,3,4,5,6,7,8),labels=c(0,1,2,3,4,5,6,7,8))
datos_test$bathrooms_group <- as.numeric(as.character(datos_test$bathrooms_group))
datos_test$log_sqft_living <- log10(datos_test$sqft_living)
datos_test$log_lot <- log10(datos_test$sqft_lot)
datos_test$log_above <- log10(datos_test$sqft_above)
datos_test$sqft_basement_cat <- cut(datos_test$sqft_basement,breaks = c(-1,0,6000),labels=c(0,1))
datos_test$waterfront<-as.factor(datos_test$waterfront)
datos_test$view<-as.factor(datos_test$view)
datos_test$condition<-as.factor(datos_test$condition)
datos_test$grade_categ <- cut(datos_test$grade, breaks = c(0,4,9,13), labels = c(0,1,2))
datos_test$yr_renovated_catg <-cut(datos_test$yr_renovated, breaks=c(-0.5,1933, 2015), labels= c("0","1"))
datos_test$zipcode<-as.factor(datos_test$zipcode)
#codificar la variable Zipcode
datos_test$zona<-recode(datos_test$zipcode, " 98001=1; 98002=1; 98003=1; 98010=1; 98011=1; 98014=1; 98019=1; 98022=1; 98023=1; 98024=1; 98028=1; 98030=1; 98031=1; 98032=1; 98034=1; 98038=1; 98042=1; 98045=1; 98055=1; 98056=1; 98058=1; 98059=1; 98070=1; 98092=1 ;98106=1; 98108=1; 98118=1; 98125=1; 98126=1; 98133=1; 98144=1; 98146=1; 98148=1; 98155=1; 98166=1; 98168=1; 98178=1; 98188=1; 98198=1; 98004=2; 98005=2; 98006=2; 98007=2; 98008=2; 98027=2; 98029=2; 98033=2; 98039=2; 98040=2; 98052=2; 98053=2; 98065=2; 98072=2; 98074=2; 98075=2; 98077=2; 98102=2; 98103=2; 98105=2; 98107=2; 98109=2; 98112=2; 98115=2; 98116=2; 98117=2; 98119=2; 98122=2; 98136=2; 98177=2; 98199=2")
datos_test$zipcode = NULL
datos_test$bathrooms_group <- cut(datos_test$bathrooms_group, breaks = c(-1,2.5,8),labels=c(0,1))
datos_test_limpio <- datos_test[c(3,21:25,8:10,26,16,17,28,27,20)]
datos_test_limpio$log_above = NULL
datos_test_numeric <- datos_test_limpio %>% select_if(is.numeric)
Realizamos todas las transformaciones y categorizaciones para el conjunto de datos de Validación.
datos_validacion <- datos_validacion[,-2]
datos_validacion$id <- as.factor(datos_validacion$id)
datos_validacion$bathrooms_group <- cut(datos_validacion$bathrooms,breaks = c(-1,0.25,1,2,3,4,5,6,7,8),labels=c(0,1,2,3,4,5,6,7,8))
datos_validacion$bathrooms_group <- as.numeric(as.character(datos_validacion$bathrooms_group))
datos_validacion$log_sqft_living <- log10(datos_validacion$sqft_living)
datos_validacion$log_lot <- log10(datos_validacion$sqft_lot)
datos_validacion$log_above <- log10(datos_validacion$sqft_above)
datos_validacion$sqft_basement_cat <- cut(datos_validacion$sqft_basement,breaks = c(-1,0,6000),labels=c(0,1))
datos_validacion$waterfront<-as.factor(datos_validacion$waterfront)
datos_validacion$view<-as.factor(datos_validacion$view)
datos_validacion$condition<-as.factor(datos_validacion$condition)
datos_validacion$grade_categ <- cut(datos_validacion$grade, breaks = c(0,4,9,13), labels = c(0,1,2))
datos_validacion$yr_renovated_catg <-cut(datos_validacion$yr_renovated, breaks=c(-0.5,1933, 2015), labels= c("0","1"))
datos_validacion$zipcode<-as.factor(datos_validacion$zipcode)
#codificar la variable Zipcode
datos_validacion$zona<-recode(datos_validacion$zipcode, " 98001=1; 98002=1; 98003=1; 98010=1; 98011=1; 98014=1; 98019=1; 98022=1; 98023=1; 98024=1; 98028=1; 98030=1; 98031=1; 98032=1; 98034=1; 98038=1; 98042=1; 98045=1; 98055=1; 98056=1; 98058=1; 98059=1; 98070=1; 98092=1 ;98106=1; 98108=1; 98118=1; 98125=1; 98126=1; 98133=1; 98144=1; 98146=1; 98148=1; 98155=1; 98166=1; 98168=1; 98178=1; 98188=1; 98198=1; 98004=2; 98005=2; 98006=2; 98007=2; 98008=2; 98027=2; 98029=2; 98033=2; 98039=2; 98040=2; 98052=2; 98053=2; 98065=2; 98072=2; 98074=2; 98075=2; 98077=2; 98102=2; 98103=2; 98105=2; 98107=2; 98109=2; 98112=2; 98115=2; 98116=2; 98117=2; 98119=2; 98122=2; 98136=2; 98177=2; 98199=2")
datos_validacion$zipcode = NULL
datos_validacion$bathrooms_group <- cut(datos_validacion$bathrooms_group, breaks = c(-1,2.5,8),labels=c(0,1))
datos_validacion_limpio <- datos_validacion[c(3,21:25,8:10,26,16,17,28,27,20)]
datos_validacion_limpio$log_above = NULL
datos_validacion_numeric <- datos_validacion_limpio %>% select_if(is.numeric)
La distribución de las casas según la nueva categorización daría como resultado un 57,8% de casas baratas (B1) y un 42,23% de casas caras (C1):
datos_train_limpio %>%
group_by(price_categ1) %>%
summarise(Count = n())%>%
mutate(percent = prop.table(Count)*100)%>%
ggplot(aes(reorder(price_categ1, - percent), percent), fill = price_categ1) +
geom_col(fill = c("salmon", "cyan3")) +
xlab("Precio de las casas") +
ylab("Porcentaje") +
ggtitle("Porcentaje de casas por precio") +
theme(plot.title = element_text(hjust = 0.5))
En cuanto a la relación de las variables que hacen referencia a las características de las casas con la variable respuesta categorizada, se observa lo siguiente:
p1<-ggplot(data = datos_train_limpio)+
geom_bar(aes(x=bedrooms,fill=price_categ1,bins=30, position = "identity"))
p2<-ggplot(data=datos_train_limpio)+
geom_bar(aes(x=bathrooms_group,fill = price_categ1))
p3<-ggplot(data=datos_train_limpio)+
geom_density(aes(x=log_sqft_living, fill=price_categ1))
p4<-ggplot(data=datos_train_limpio)+
geom_density(aes(x=log_lot, fill=price_categ1))+
facet_grid(price_categ1~., scales = 'free')
grid.arrange(p1, p2, p3, p4, nrow = 2)
Por último, para la relación entre las variables que expresan la calidad de las casas y el precio, se obtienen los siguientes resultados:
p5<-ggplot(data = datos_train_limpio) + geom_bar(aes(x=waterfront,fill=price_categ1, stat="count"))
p6<-ggplot(data = datos_train_limpio) + geom_bar(aes(x=view,fill=price_categ1, stat="count"))
p7<-ggplot(data = datos_train_limpio) + geom_bar(aes(x=condition,fill=price_categ1, stat="count"))
p8<-ggplot(data = datos_train_limpio) + geom_bar(aes(x=grade_categ,fill=price_categ1, stat="count"))
p9<-ggplot(data = datos_train_limpio) + geom_bar(aes(x=yr_renovated_catg,fill=price_categ1, stat="count"))
p10<-ggplot(data = datos_train_limpio) + geom_bar(aes(x=zona,fill=price_categ1,bins=30, position = "identity"))
grid.arrange(p5,p6,p7,p8,p9,p10, nrow = 3)
Es un método que permite simplificar la complejidad de espacios muestrales con muchas dimensiones conservando la mayor cantidad de información posible.
pca<-prcomp(datos_train_numeric,scale=T)
plot(pca, main = 'Análisis de componentes principales', xlab= 'Componente')
summary(pca)
## Importance of components:
## PC1 PC2 PC3 PC4 PC5
## Standard deviation 1.414 1.0885 0.9290 0.7850 0.57947
## Proportion of Variance 0.400 0.2369 0.1726 0.1232 0.06716
## Cumulative Proportion 0.400 0.6370 0.8096 0.9328 1.00000
pca$rotation
## PC1 PC2 PC3 PC4 PC5
## bedrooms 0.5170755 -0.4142446 0.36490069 0.108832991 0.64500949
## log_sqft_living 0.5825593 -0.3343737 0.09165417 0.008721066 -0.73507980
## log_lot 0.4612158 0.3615021 -0.29757928 -0.737528449 0.15522444
## lat -0.1072636 -0.6381109 -0.75248337 -0.052915963 0.11080474
## long 0.4111352 0.4227604 -0.45128965 0.664327488 0.08513595
biplot(x = pca, scale = 0, cex = 0.6, col = c("blue4", "brown3"))
Se observa, como la componente 1 (PC1) está diferenciando lat (-0.1) frente a long (0.41), log_lot (0.46), log_sqft_living (0.58) y bedrooms (0.51). La componente 2 (PC2) por otro lado estaría capturando la diferencia entre long y log_lot, frente a lat, log_sqft_livingy bedrooms. Estas conclusiones se extraen de los pesos que cada autovector da a cada variable.
A continuación, se implementan tres métodos de agrupamiento no supervisado. Primero, un método jerárquico para averiguar (si es posible) la cantidad adecuada de clústers y, a continuación, K-Means y K-Medoids. La agrupación es una técnica para agrupar puntos de datos similares en un grupo y separar las diferentes observaciones en diferentes grupos, de forma que un mismo cluster agrupe casas con características homogéneas que se diferencien en cierta medida del resto de clústers.
En el Clustering Jerárquico los clústers se crean de manera que tengan un orden predeterminado. En nuestro estudio se va a aplicar un método aglomerativo que consiste en que cada observación se asigna a su propio clúster. Luego, se calcula la similitud (o distancia) entre cada uno de los clústers y los dos clústers más similares se fusionan en uno. Finalmente, los pasos 2 y 3 se repiten hasta que solo quede un grupo.
Aplicando este método a nuestros datos queremos observar si siguen algún patrón para poder agrupar las casas.
Escalamos las variables numéricas, es decir, cada variable ahora tendrá una media cero y una desviación estándar uno. Por otro lado, se halla la matriz de distancias mediante la función dist que usa por defecto la ‘Euclidea’.
datos_scale<- as.data.frame(scale(datos_train_numeric))
matriz_dist=dist(datos_scale)
En este caso particular, probamos con dos clústers dado que la categorización del precio se hace en base a dos clases (caras y baratas):
set.seed(1234)
modelo_hc= hclust(matriz_dist, method = "average")
plot(modelo_hc, sub='')
rect.hclust(modelo_hc, k=2, border="red")
A continuación vemos cómo se han agrupado los datos marcando que el número de clústers sea 2. Como se puede ver, prácticamente todas las casas están en el grupo 1.
grupos2=cutree(modelo_hc,k=2)
table(datos_train_limpio$price_categ1, grupos2)
## grupos2
## 1 2
## B1 8724 10
## C1 6385 0
Se concluye que la agrupación en dos clases está muy descompensada, obteniendo un total de 15.109 casas en el grupo 1 y 10 en el grupo 2. Dados estos resultados, se decide que no es un método adecuado para los datos con los que contamos y por tanto, no se evaluan los conjuntos de test y validación.
El método de K-Means basa su funcionamiento en agrupar los datos de entrada en un total de k conjuntos definidos por un centroide, cuya distancia con los puntos que pertenecen a cada uno de los datos sea la menor posible.
Primero se van a realizar los gráficos para ver cómo están diferenciadas las casas por precio. Para poder visualizarlo en dos dimensiones se ha usado la función “prcomp” (PCA)
colores1= c("red","blue")
colores11 = colores1[datos_train_limpio$price_categ1]
plot(prcomp(datos_train_numeric, scale = T)$x[,1:2], type="n",main= "Dos categorías")
text(prcomp(datos_train_numeric, scale = T)$x[,1:2], as.character(datos_train_limpio$price_categ1), col=colores11)
A continuación se va a aplicar el método de agrupamiento K-means, al igual que en el método anterior se le pasa la matriz de distacias y se van a agrupar los datos en dos conjuntos:
set.seed(1234)
model_km <- kmeans(matriz_dist, centers=2)
table(model_km$cluster) #asignación de observación a los cluster
##
## 1 2
## 3926 11193
colores1= c("red","blue")
colores11 = colores1[datos_train_limpio$price_categ1]
plot(prcomp(datos_train_numeric, scale = T)$x[,1:2], type="n")
text(prcomp(datos_train_numeric, scale = T)$x[,1:2], as.character(model_km$cluster), col=colores11)
A continuación, para visualizar si el agrupamiento que se ha llevado a cabo está relacionado con el precio de las casas (Caras, Baratas), se representa en el mapa que se muestra a continuación.
clusterkmeans=as.data.frame(model_km$cluster)
clusterkmeans$indice= as.integer(rownames(clusterkmeans))
colnames(clusterkmeans)[1]= "categoria_price_km"
clustering1= clusterkmeans[order(clusterkmeans$indice),]
center_lon = median(datos_train_limpio$long,na.rm = TRUE)
center_lat = median(datos_train_limpio$lat,na.rm = TRUE)
factpal1 <- colorFactor(c("green","red"),
clustering1$categoria_price_km )
leaflet(datos_train_limpio) %>% addProviderTiles("Esri.NatGeoWorldMap") %>%
addCircles(lng = ~long, lat = ~lat,
color = ~factpal1(clustering1$categoria_price_km)) %>%
# controls
setView(lng=center_lon, lat=center_lat,zoom = 9) %>%
addLegend("bottomright", pal = factpal1 , values = ~clustering1$categoria_price_km,
title = "Tipos de casas",
opacity = 1)
Comparando el mapa con el de los datos iniciales, dista bastante. Por lo que deducimos que la agrupación que está realizando K-Means con \(k=2\) no sigue ningún patrón. Como ya se suponía, la naturaleza de los datos no permiten una agrupación de los mismos, por ejemplo, una casa cara puede tener el mismo número de pies cuadrado que una barata, que esté situada en otro barrio
Se va a determinar la cantidad óptima de centroides a utilizar a partir del Método del Codo. Para ello, aplicaremos la función kmeans al conjunto de datos, variando en cada caso el valor de k y acumulando los valores de WCSS (Within-Cluster-Sum-of-Squares) obtenidos:
set.seed(1234)
wcss <- vector()
for(i in 1:20){
wcss[i] <- sum(kmeans(datos_scale, i)$withinss)
}
ggplot() + geom_point(aes(x = 1:20, y = wcss), color = 'blue') +
geom_line(aes(x = 1:20, y = wcss), color = 'blue') +
ggtitle("Método del Codo") +
xlab('Cantidad de Centroides k') +
ylab('WCSS')
Como se observa en la gráfica, el K-óptimo que se podría aplicar sería de 10. Después de volver a implementar el modelo con \(k=10\), se concluye que sigue sin haber un patrón que permita agrupas las casas en función de sus características.
K-medoids es un método de clustering muy similar a K-means en cuanto a que ambos agrupan las observaciones en K clústers, donde K es un valor preestablecido. La diferencia es que, en K-medoids, cada clúster está representado por una observación presente en el clúster (medoid). En nuestro estudio será una observación de una casa, mientras que en K-means cada clúster está representado por su centroide, que se corresponde con el promedio de todas las observaciones del clúster pero con ninguna casa en particular.
Este algoritmo es menos sensible al ruido y a los valores atípicos en comparación con k-means, porque usa medoides como centros de clúster en lugar de centroides (utilizados en k-means).
datoskmedoids = datos_train_limpio[,-15]
model_medoids = pam(x = datoskmedoids, k = 2, keep.diss = TRUE, keep.data = TRUE)
model_medoids$medoids
## bedrooms bathrooms_group log_sqft_living log_lot sqft_basement_cat
## 9606 3 1 3.152288 3.870404 1
## 8735 4 2 3.406540 3.936111 2
## waterfront view condition grade_categ lat long zona
## 9606 1 1 3 2 47.4949 -122.239 1
## 8735 1 1 3 2 47.6477 -122.197 2
## yr_renovated_catg price_categ1
## 9606 1 1
## 8735 1 2
Una vez más se representa el mapa para comprobar los resultados de la agrupación y pese a que los medoids aparecían separados, la clasificación es muy heterogénea y no se corresponde con la categorización del precio, por tanto se descarta este modelo.
clustering= sort(model_medoids$clustering)
clustering=as.data.frame(model_medoids$clustering)
clustering$indice= as.integer(rownames(clustering))
colnames(clustering)[1]= "categoria_price"
clustering2= clustering[order(clustering$indice),]
center_lon = median(datoskmedoids$long,na.rm = TRUE)
center_lat = median(datoskmedoids$lat,na.rm = TRUE)
factpal2 <- colorFactor(c("green","red"),
clustering2$categoria_price )
leaflet(datoskmedoids) %>% addProviderTiles("Esri.NatGeoWorldMap") %>%
addCircles(lng = ~long, lat = ~lat,
color = ~factpal2(clustering2$categoria_price)) %>%
# controls
setView(lng=center_lon, lat=center_lat,zoom = 9) %>%
addLegend("bottomright", pal = factpal2 , values = ~clustering2$categoria_price,
title = "Tipos de casas",
opacity = 1)
Con este modelo se va a estudiar si existe relación entre el hecho de que una casa sea “cara” ó “barata” dependiendo de las características de las casas. Se va a generar un modelo en el que a partir de las variables prediga la probabilidad de que una casa sea barata o cara.
Inicialmente, se probó con todas las variables y después de implementar y evaluar varios modelos, se concluyó que las variables condition y grade_categ no eran importantes, por tanto se eliminaron para este modelo.
datos_train_rl <- datos_train_limpio[,-c(8,9)]
datos_train_rl$price_categ1<- recode(datos_train_rl$price_categ1, "'B1'=0; 'C1'=1")
set.seed(1234)
model_glm = glm(price_categ1 ~., family = binomial, data =datos_train_rl )
summary(model_glm)
##
## Call:
## glm(formula = price_categ1 ~ ., family = binomial, data = datos_train_rl)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -3.3536 -0.3998 -0.0883 0.3481 3.6096
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -759.91116 32.36457 -23.480 < 2e-16 ***
## bedrooms -0.23062 0.03834 -6.015 1.80e-09 ***
## bathrooms_group1 0.14717 0.06828 2.155 0.03113 *
## log_sqft_living 13.13156 0.32565 40.324 < 2e-16 ***
## log_lot 0.66619 0.08223 8.101 5.45e-16 ***
## sqft_basement_cat1 -0.45846 0.05867 -7.814 5.52e-15 ***
## waterfront1 1.35682 0.62801 2.161 0.03073 *
## view1 1.13634 0.22417 5.069 4.00e-07 ***
## view2 1.21640 0.14137 8.605 < 2e-16 ***
## view3 1.84563 0.21019 8.781 < 2e-16 ***
## view4 2.49482 0.49846 5.005 5.58e-07 ***
## lat 5.32574 0.24476 21.759 < 2e-16 ***
## long -3.75931 0.24366 -15.429 < 2e-16 ***
## zona2 3.33880 0.07036 47.451 < 2e-16 ***
## yr_renovated_catg1 0.37667 0.13792 2.731 0.00631 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 20592.9 on 15118 degrees of freedom
## Residual deviance: 8975.8 on 15104 degrees of freedom
## AIC: 9005.8
##
## Number of Fisher Scoring iterations: 6
La métrica que se ha considerado para evaluar los modelos es la F1-medida, por un lado para las casas caras, y por otro, para las baratas. Después se realiza la media: \(\frac{\text{F1-medida}_{caras} + \text{F1-medida}_{baratas}}{2}\)
# EVALUACION
predicciones <- ifelse(test = model_glm$fitted.values > 0.5, yes = 1, no = 0)
tabla_glm_train <- table(model_glm$model$price_categ1, predicciones,
dnn = c("observaciones", "predicciones"))
tabla_glm_train
## predicciones
## observaciones 0 1
## 0 7813 921
## 1 1010 5375
#caras
pred_caras_glm_train=tabla_glm_train[2,2]/(tabla_glm_train[2,2]+tabla_glm_train[1,2])
rec_caras_glm_train=tabla_glm_train[2,2]/(tabla_glm_train[2,2]+tabla_glm_train[2,1])
F_caras_reglog=(2*pred_caras_glm_train*rec_caras_glm_train)/(pred_caras_glm_train+rec_caras_glm_train)
cat(c('F1 casas caras: ', F_caras_reglog), '\n')
## F1 casas caras: 0.847724942827853
#baratas
pred_baratas_glm_train=tabla_glm_train[1,1]/(tabla_glm_train[1,1]+tabla_glm_train[2,1])
rec_baratas_glm_train=tabla_glm_train[1,1]/(tabla_glm_train[1,1]+tabla_glm_train[1,2])
F_baratas_reglog=(2*pred_baratas_glm_train*rec_baratas_glm_train)/(pred_baratas_glm_train+rec_baratas_glm_train)
cat(c('F1 casas baratas: ', F_baratas_reglog), '\n')
## F1 casas baratas: 0.890015378481517
#F-Medida
F_reglog_train= (F_caras_reglog+F_baratas_reglog)/2
cat(c('F1 global: ', F_reglog_train), '\n')
## F1 global: 0.868870160654685
accuracy_reglog_train = (tabla_glm_train[1,1]+tabla_glm_train[2,2]) / (tabla_glm_train[1,1]+tabla_glm_train[1,2]+tabla_glm_train[2,1]+tabla_glm_train[2,2])
cat(c('Accuracy: ', accuracy_reglog_train), '\n')
## Accuracy: 0.872279912692638
El resultado de la F1-medida para el modelo GLM considerando las variables: bedrooms, bathrooms_group, log_sqft_living, log_lot, sqft_basement_cat, waterfront, view, lat, lon y zona, es de 0.87, y lo mismo para la accuracy. Este modelo, al ser supervisado, mejora bastante la clasificación de las casas en función de sus características.
Este método se basa en clasificar cada dato en un grupo en función de sus k vecinos más cercanos. Para ello se usa la distancia de cada nuevo punto a los ya existentes. Para ajustar y comparar este modelo, se han usado tres métodos:
En primer lugar con la función train.kknn se obtiene de manera automática el mejor valor de k hasta un máximo de \(k=20\).
set.seed(1234)
suppressWarnings(suppressMessages(library(kknn)))
knn_1 <- train.kknn(price_categ1 ~ ., data = datos_train_limpio, kmax = 20)
knn_1
##
## Call:
## train.kknn(formula = price_categ1 ~ ., data = datos_train_limpio, kmax = 20)
##
## Type of response variable: nominal
## Minimal misclassification: 0.1148886
## Best kernel: optimal
## Best k: 15
En segundo lugar se usa la función tune que itera hasta \(k=20\) y muestra el error y la dispersión para cada valor de k.
set.seed(1234)
knn_2 <- tune.knn(x=scale(datos_train_numeric),
y=as.factor(datos_train_limpio$price_categ1), k = 1:20,
tunecontrol = tune.control(sampling = "boot"))
summary(knn_2)
##
## Parameter tuning of 'knn.wrapper':
##
## - sampling method: bootstrapping
##
## - best parameters:
## k
## 20
##
## - best performance: 0.1245178
##
## - Detailed performance results:
## k error dispersion
## 1 1 0.1438725 0.003032594
## 2 2 0.1488328 0.004107803
## 3 3 0.1464719 0.003574503
## 4 4 0.1439387 0.004097428
## 5 5 0.1376046 0.003948778
## 6 6 0.1363546 0.003548451
## 7 7 0.1332349 0.003366625
## 8 8 0.1329151 0.003934370
## 9 9 0.1288270 0.002981856
## 10 10 0.1286652 0.003300452
## 11 11 0.1273676 0.003302001
## 12 12 0.1269916 0.004677623
## 13 13 0.1258752 0.004332600
## 14 14 0.1269473 0.003960220
## 15 15 0.1262791 0.003599693
## 16 16 0.1249677 0.004310555
## 17 17 0.1255292 0.004179737
## 18 18 0.1256711 0.003900466
## 19 19 0.1251843 0.003735257
## 20 20 0.1245178 0.003857497
plot(knn_2, main = "Mejor k según tune")
En tercer lugar, se comprueba manualmente los resultados de la F1-medida con diferentes modelos desde \(k=1\) hasta el \(k=50\) y se representa el resultado.
set.seed(1234)
k_maximo=50
rango=1:k_maximo
f1_modelos=c()
for (i in rango){
knn_3=knn.cv(scale(datos_train_numeric),cl=as.factor(datos_train_limpio$price_categ1),k=i)
tabla=table(datos_train_limpio$price_categ1,knn_3)
# f1 casas caras
pred_means_caras=tabla[2,2]/(tabla[2,2]+tabla[1,2])
rec_means_caras=tabla[2,2]/(tabla[2,2]+tabla[2,1])
f1_caras=(2*pred_means_caras*rec_means_caras)/(pred_means_caras+rec_means_caras)
# f1 casas baratas
pred_means_baratas=tabla[1,1]/(tabla[1,1]+tabla[2,1])
rec_means_baratas=tabla[1,1]/(tabla[1,1]+tabla[1,2])
f1_baratas=(2*pred_means_baratas*rec_means_baratas)/(pred_means_baratas+rec_means_baratas)
f1_total = (f1_baratas + f1_caras)/2
f1_modelos=c(f1_modelos,f1_total)
}
plot(f1_modelos)
cat(c('Valor óptimo de k: ', which.max(f1_modelos)))
## Valor óptimo de k: 17
De la primer forma obtenemos un valor óptimo de \(k=15\), del segundo modo a partir de aproximadamente \(k=15\) el error es muy pequeño, y de la forma manual, concluimos que el mejor valor de k en función de la F1-medida es de \(k=17\). Finalmente, elegimos el valor de \(k=17\), implementamos el modelo y lo evaluamos.
model_knn=knn.cv(scale(datos_train_numeric),cl=as.factor(datos_train_limpio$price_categ1),k=17, prob = TRUE)
center_lon = median(datos_train_limpio$long,na.rm = TRUE)
center_lat = median(datos_train_limpio$lat,na.rm = TRUE)
factpal1 <- colorFactor(c("green","red"),
model_knn)
leaflet(datos_train_limpio) %>% addProviderTiles("Esri.NatGeoWorldMap") %>%
addCircles(lng = ~long, lat = ~lat,
color = ~factpal1(model_knn)) %>%
# controls
setView(lng=center_lon, lat=center_lat, zoom = 9) %>%
addLegend("bottomright", pal = factpal1 , values = ~model_knn,
title = "Precio (en miles de $)",
opacity = 1)
tabla_knn_train=table(datos_train_limpio$price_categ1, model_knn)
tabla_knn_train
## model_knn
## B1 C1
## B1 7876 858
## C1 915 5470
pred_caras_knn_train = tabla_knn_train[2,2]/(tabla_knn_train[2,2]+tabla_knn_train[1,2])
rec_caras_knn_train = tabla_knn_train[2,2]/(tabla_knn_train[2,2]+tabla_knn_train[2,1])
F_caras_knn_train=(2*pred_caras_knn_train*rec_caras_knn_train)/(pred_caras_knn_train+rec_caras_knn_train)
cat(c('F1 caras: ', F_caras_knn_train), '\n')
## F1 caras: 0.860536458743019
pred_baratas_knn_train=tabla_knn_train[1,1]/(tabla_knn_train[1,1]+tabla_knn_train[2,1])
rec_baratas_knn_train=tabla_knn_train[1,1]/(tabla_knn_train[1,1]+tabla_knn_train[1,2])
F_baratas_knn_train=(2*pred_baratas_knn_train*rec_baratas_knn_train)/(pred_baratas_knn_train+rec_baratas_knn_train)
cat(c('F1 baratas: ', F_baratas_knn_train), '\n')
## F1 baratas: 0.898830242510699
F_knn_train= (F_caras_knn_train+F_baratas_knn_train)/2
cat(c('F1 global: ', F_knn_train), '\n')
## F1 global: 0.879683350626859
accuracy_knn_train= (tabla_knn_train[1,1]+tabla_knn_train[2,2]) / (tabla_knn_train[1,1]+tabla_knn_train[1,2]+tabla_knn_train[2,1]+tabla_knn_train[2,2])
cat(c('Accuracy: ', accuracy_knn_train), '\n')
## Accuracy: 0.882730339308155
Como los valores de k óptimos son muy parecidos entre las diferentes técnicas usadas para determinar este parámetro, se elige \(k=17\), con los que se obtiene una F1-medida y accuracy de 0.88.
tabla_knn_test=table(datos_test_limpio$price_categ1, knn(scale(datos_train_numeric), scale(datos_test_numeric),cl=datos_train_limpio$price_categ1,k=17))
tabla_knn_test
##
## B1 C1
## B1 1701 210
## C1 187 1143
pred_caras_knn_test=tabla_knn_test[2,2]/(tabla_knn_test[2,2]+tabla_knn_test[1,2])
rec_caras_knn_test=tabla_knn_test[2,2]/(tabla_knn_test[2,2]+tabla_knn_test[2,1])
F_caras_knn_test=(2*pred_caras_knn_test*rec_caras_knn_test)/(pred_caras_knn_test+rec_caras_knn_test)
cat(c('F1 caras: ', F_caras_knn_test), '\n')
## F1 caras: 0.852031308237048
pred_baratas_knn_test=tabla_knn_test[1,1]/(tabla_knn_test[1,1]+tabla_knn_test[2,1])
rec_baratas_knn_test=tabla_knn_test[1,1]/(tabla_knn_test[1,1]+tabla_knn_test[1,2])
F_baratas_knn_test=(2*pred_baratas_knn_test*rec_baratas_knn_test)/(pred_baratas_knn_test+rec_baratas_knn_test)
cat(c('F1 baratas: ', F_baratas_knn_test), '\n')
## F1 baratas: 0.895498815477757
F_knn_test = (F_caras_knn_test+F_baratas_knn_test)/2
cat(c('F1 global: ', F_knn_test), '\n')
## F1 global: 0.873765061857403
accuracy_knn_test= (tabla_knn_test[1,1]+tabla_knn_test[2,2]) / (tabla_knn_test[1,1]+tabla_knn_test[1,2]+tabla_knn_test[2,1]+tabla_knn_test[2,2])
cat(c('Accuracy: ', accuracy_knn_test), '\n')
## Accuracy: 0.877506942301759
tabla_knn_validacion=table(datos_validacion_limpio$price_categ1, knn(scale(datos_train_numeric), scale(datos_validacion_numeric),cl=datos_train_limpio$price_categ1,k=17))
tabla_knn_validacion
##
## B1 C1
## B1 1727 179
## C1 203 1134
pred_caras_knn_validacion=tabla_knn_validacion[2,2]/(tabla_knn_validacion[2,2]+tabla_knn_validacion[1,2])
rec_caras_knn_validacion=tabla_knn_validacion[2,2]/(tabla_knn_validacion[2,2]+tabla_knn_validacion[2,1])
F_caras_knn_validacion=(2*pred_caras_knn_validacion*rec_caras_knn_validacion)/(pred_caras_knn_validacion+rec_caras_knn_validacion)
cat(c('F1 caras: ', F_caras_knn_validacion), '\n')
## F1 caras: 0.855849056603774
pred_baratas_knn_validacion=tabla_knn_validacion[1,1]/(tabla_knn_validacion[1,1]+tabla_knn_validacion[2,1])
rec_baratas_knn_validacion=tabla_knn_validacion[1,1]/(tabla_knn_validacion[1,1]+tabla_knn_validacion[1,2])
F_baratas_knn_validacion=(2*pred_baratas_knn_validacion*rec_baratas_knn_validacion)/(pred_baratas_knn_validacion+rec_baratas_knn_validacion)
cat(c('F1 baratas: ', F_baratas_knn_validacion), '\n')
## F1 baratas: 0.900417101147028
F_knn_validacion = (F_caras_knn_validacion+F_baratas_knn_validacion)/2
cat(c('F1 global: ', F_knn_validacion), '\n')
## F1 global: 0.878133078875401
accuracy_knn_validacion= (tabla_knn_validacion[1,1]+tabla_knn_validacion[2,2]) / (tabla_knn_validacion[1,1]+tabla_knn_validacion[1,2]+tabla_knn_validacion[2,1]+tabla_knn_validacion[2,2])
cat(c('Accuracy: ', accuracy_knn_validacion), '\n')
## Accuracy: 0.882207832254086
Los árboles de decisión son un método usado en distintas disciplinas como modelo de predicción. Este método es similar a diagramas de flujo, en los que llegamos a puntos donde se toman decisiones de acuerdo a una regla. De manera general, lo que hace este algoritmo es encontrar la variable independiente que mejor separa nuestros datos en grupos, que corresponden con las categorías de la variable objetivo, en nuestro caso casas caras frente a baratas.
Como en este modelo se toman decisiones en base a puntos de corte en las diferentes variables, se ha decidido eliminar la latitud y longitud dado que ya tenemos una variable zona que aporta esta misma información.
El parámetro cp (parámetro de complejidad) da una idea de lo que se penaliza añadir una nueva rama en el modelo, un cp más pequeño implica que el tamaño del árbol sea más reducido y viceversa. Cualquier división que no mejore este parámetro se eliminará mediante validación cruzada. El parámetro minbucket indica el número de casas que tienen que quedar en cualquier nodo terminal del árbol. Por último, maxdeph indica la profundidad del árbol, o número máximo de capas, contando la raiz como profundidad = 0.
#quitamos lat y long:
datos_decision_tree<-datos_train_limpio[,-c(10,11)]
decisiontree_model=rpart(price_categ1~.,
data=datos_decision_tree,
parms=list(split="gini"),
control = rpart.control(cp = 0.005, maxdepth = 5, minbucket = 400))
# print(decisiontree_model)
fancyRpartPlot(decisiontree_model, caption='')
El árbol obtenido con los parámetros indicados considera que las variables zona y log_sqft_living son las más importantes para realizar la clasificación. A continuación, se evalúa este modelo y el resultado para la F1-medida y accuracy es de 0.84.
tabla_train_arbol=table(obs = datos_decision_tree$price_categ1, pred = predict(decisiontree_model, type = "class"))
tabla_train_arbol
## pred
## obs B1 C1
## B1 7896 838
## C1 1461 4924
pred_caras_dt_train = tabla_train_arbol[2,2]/(tabla_train_arbol[2,2]+tabla_train_arbol[1,2])
rec_caras_dt_train = tabla_train_arbol[2,2]/(tabla_train_arbol[2,2]+tabla_train_arbol[2,1])
F_caras_dt_train=(2*pred_caras_dt_train*rec_caras_dt_train)/(pred_caras_dt_train+rec_caras_dt_train)
cat(c('F1 caras: ', F_caras_dt_train), '\n')
## F1 caras: 0.810735160945089
pred_baratas_dt_train = tabla_train_arbol[1,1]/(tabla_train_arbol[1,1]+tabla_train_arbol[2,1])
rec_baratas_dt_train = tabla_train_arbol[1,1]/(tabla_train_arbol[1,1]+tabla_train_arbol[1,2])
F_baratas_dt_train=(2*pred_baratas_dt_train*rec_baratas_dt_train)/(pred_baratas_dt_train+rec_baratas_dt_train)
cat(c('F1 baratas: ', F_baratas_dt_train), '\n')
## F1 baratas: 0.872920236581726
F_dt_train= (F_caras_dt_train+F_baratas_dt_train)/2
cat(c('F1 global: ', F_dt_train), '\n')
## F1 global: 0.841827698763407
accuracy_dt_train = (tabla_train_arbol[1,1]+tabla_train_arbol[2,2]) / (tabla_train_arbol[1,1]+tabla_train_arbol[1,2]+tabla_train_arbol[2,1]+tabla_train_arbol[2,2])
cat(c('Accuracy: ', F_dt_train), '\n')
## Accuracy: 0.841827698763407
tabla_test_arbol=table(obs = datos_test_limpio$price_categ1, pred = predict(decisiontree_model, datos_test_limpio[,-15], type = "class"))
tabla_test_arbol
## pred
## obs B1 C1
## B1 1699 212
## C1 302 1028
pred_caras_dt_test = tabla_test_arbol[2,2]/(tabla_test_arbol[2,2]+tabla_test_arbol[1,2])
rec_caras_dt_test = tabla_test_arbol[2,2]/(tabla_test_arbol[2,2]+tabla_test_arbol[2,1])
F_caras_dt_test=(2*pred_caras_dt_test*rec_caras_dt_test)/(pred_caras_dt_test+rec_caras_dt_test)
cat(c('F1 caras: ', F_caras_dt_test), '\n')
## F1 caras: 0.8
pred_baratas_dt_test = tabla_test_arbol[1,1]/(tabla_test_arbol[1,1]+tabla_test_arbol[2,1])
rec_baratas_dt_test = tabla_test_arbol[1,1]/(tabla_test_arbol[1,1]+tabla_test_arbol[1,2])
F_baratas_dt_test=(2*pred_baratas_dt_test*rec_baratas_dt_test)/(pred_baratas_dt_test+rec_baratas_dt_test)
cat(c('F1 baratas: ', F_baratas_dt_test), '\n')
## F1 baratas: 0.868609406952965
F_dt_test= (F_caras_dt_test+F_baratas_dt_test)/2
cat(c('F1 global: ', F_dt_test), '\n')
## F1 global: 0.834304703476483
accuracy_dt_test = (tabla_test_arbol[1,1]+tabla_test_arbol[2,2]) / (tabla_test_arbol[1,1]+tabla_test_arbol[1,2]+tabla_test_arbol[2,1]+tabla_test_arbol[2,2])
cat(c('Accuracy: ', F_dt_test), '\n')
## Accuracy: 0.834304703476483
tabla_validacion_arbol=table(obs = datos_validacion_limpio$price_categ1, pred = predict(decisiontree_model, datos_validacion_limpio[,-15], type = "class"))
tabla_validacion_arbol
## pred
## obs B1 C1
## B1 1709 197
## C1 301 1036
pred_caras_dt_validacion = tabla_validacion_arbol[2,2]/(tabla_validacion_arbol[2,2]+tabla_validacion_arbol[1,2])
rec_caras_dt_validacion = tabla_validacion_arbol[2,2]/(tabla_validacion_arbol[2,2]+tabla_validacion_arbol[2,1])
F_caras_dt_validacion=(2*pred_caras_dt_validacion*rec_caras_dt_validacion)/(pred_caras_dt_validacion+rec_caras_dt_validacion)
cat(c('F1 caras: ', F_caras_dt_validacion), '\n')
## F1 caras: 0.806225680933852
pred_baratas_dt_validacion = tabla_validacion_arbol[1,1]/(tabla_validacion_arbol[1,1]+tabla_validacion_arbol[2,1])
rec_baratas_dt_validacion = tabla_validacion_arbol[1,1]/(tabla_validacion_arbol[1,1]+tabla_validacion_arbol[1,2])
F_baratas_dt_validacion=(2*pred_baratas_dt_validacion*rec_baratas_dt_validacion)/(pred_baratas_dt_validacion+rec_baratas_dt_validacion)
cat(c('F1 baratas: ', F_baratas_dt_validacion), '\n')
## F1 baratas: 0.872829417773238
F_dt_validacion= (F_caras_dt_validacion+F_baratas_dt_validacion)/2
cat(c('F1 global: ', F_dt_validacion), '\n')
## F1 global: 0.839527549353545
accuracy_dt_validacion = (tabla_validacion_arbol[1,1]+tabla_validacion_arbol[2,2]) / (tabla_validacion_arbol[1,1]+tabla_validacion_arbol[1,2]+tabla_validacion_arbol[2,1]+tabla_validacion_arbol[2,2])
cat(c('Accuracy: ', F_dt_validacion), '\n')
## Accuracy: 0.839527549353545
Los Random forests son una combinación de árboles predictores tal que cada árbol depende de los valores de un vector aleatorio probado independientemente y con la misma distribución para cada uno de estos. Es una modificación sustancial de bagging que construye una larga colección de árboles no correlacionados y luego los promedia.
La implementación de este modelo se realiza mediante la función randomForest, y los parámetros de esta función son: ntree que indica el número de árboles que se van a considerar; importance que indica si se considerará la importancia de los predictores; proximity que indica si se calculará o no la proximidad entre las filas (vectores de características de las casas); mtry para indicar el número de variables que serán seleccionadas aleatoriamente en cada división; y replace para indicar si las muestras serán tomadas con o sin reemplazamiento.
set.seed(1234)
randomforest_model=randomForest(price_categ1~.,
data=datos_train_limpio,
parms=list(split="gini"),
ntree=200,
importance = FALSE,
proximity = FALSE,
mtry=6,
replace = TRUE)
print(randomforest_model)
##
## Call:
## randomForest(formula = price_categ1 ~ ., data = datos_train_limpio, parms = list(split = "gini"), ntree = 200, importance = FALSE, proximity = FALSE, mtry = 6, replace = TRUE)
## Type of random forest: classification
## Number of trees: 200
## No. of variables tried at each split: 6
##
## OOB estimate of error rate: 9.56%
## Confusion matrix:
## B1 C1 class.error
## B1 7999 735 0.08415388
## C1 710 5675 0.11119812
Después de implementar el modelo, se evalua, obteniendo una F1-medida y accuracy de 0.9, el mejor resultado obtenido hasta el momento.
tabla_train_randomforest = table(obs = datos_train_limpio$price_categ1, pred = predict(randomforest_model, type = "class") )
tabla_train_randomforest
## pred
## obs B1 C1
## B1 7999 735
## C1 710 5675
pred_caras_rf_train = tabla_train_randomforest[2,2]/(tabla_train_randomforest[2,2]+tabla_train_randomforest[1,2])
rec_caras_rf_train = tabla_train_randomforest[2,2]/(tabla_train_randomforest[2,2]+tabla_train_randomforest[2,1])
F_caras_rf_train=(2*pred_caras_rf_train*rec_caras_rf_train)/(pred_caras_rf_train+rec_caras_rf_train)
cat(c('F1 caras: ', F_caras_rf_train), '\n')
## F1 caras: 0.887065259867136
pred_baratas_rf_train = tabla_train_randomforest[1,1]/(tabla_train_randomforest[1,1]+tabla_train_randomforest[2,1])
rec_baratas_rf_train=tabla_train_randomforest[1,1]/(tabla_train_randomforest[1,1]+tabla_train_randomforest[1,2])
F_baratas_rf_train=(2*pred_baratas_rf_train*rec_baratas_rf_train)/(pred_baratas_rf_train+rec_baratas_rf_train)
cat(c('F1 baratas: ', F_baratas_rf_train), '\n')
## F1 baratas: 0.917158745628619
F_rf_train= (F_caras_rf_train+F_baratas_rf_train)/2
cat(c('F1 global: ', F_rf_train), '\n')
## F1 global: 0.902112002747877
accuracy_rf_train = (tabla_train_randomforest[1,1]+tabla_train_randomforest[2,2]) / (tabla_train_randomforest[1,1]+tabla_train_randomforest[1,2]+tabla_train_randomforest[2,1]+tabla_train_randomforest[2,2])
cat(c('Accuracy: ', accuracy_rf_train), '\n')
## Accuracy: 0.904424895826444
tabla_test_randomforest = table(obs = datos_test_limpio$price_categ1, pred = predict(randomforest_model, datos_test_limpio[,-15], type = "class") )
tabla_test_randomforest
## pred
## obs B1 C1
## B1 1751 160
## C1 138 1192
pred_caras_rf_test = tabla_test_randomforest[2,2]/(tabla_test_randomforest[2,2]+tabla_test_randomforest[1,2])
rec_caras_rf_test = tabla_test_randomforest[2,2]/(tabla_test_randomforest[2,2]+tabla_test_randomforest[2,1])
F_caras_rf_test=(2*pred_caras_rf_test*rec_caras_rf_test)/(pred_caras_rf_test+rec_caras_rf_test)
cat(c('F1 caras: ', F_caras_rf_test), '\n')
## F1 caras: 0.888888888888889
pred_baratas_rf_test = tabla_test_randomforest[1,1]/(tabla_test_randomforest[1,1]+tabla_test_randomforest[2,1])
rec_baratas_rf_test=tabla_test_randomforest[1,1]/(tabla_test_randomforest[1,1]+tabla_test_randomforest[1,2])
F_baratas_rf_test=(2*pred_baratas_rf_test*rec_baratas_rf_test)/(pred_baratas_rf_test+rec_baratas_rf_test)
cat(c('F1 baratas: ', F_baratas_rf_test), '\n')
## F1 baratas: 0.921578947368421
F_rf_test= (F_caras_rf_test+F_baratas_rf_test)/2
cat(c('F1 global: ', F_rf_test), '\n')
## F1 global: 0.905233918128655
accuracy_rf_test = (tabla_test_randomforest[1,1]+tabla_test_randomforest[2,2]) / (tabla_test_randomforest[1,1]+tabla_test_randomforest[1,2]+tabla_test_randomforest[2,1]+tabla_test_randomforest[2,2])
cat(c('Accuracy: ', accuracy_rf_test), '\n')
## Accuracy: 0.908053070040111
tabla_validacion_randomforest = table(obs = datos_validacion_limpio$price_categ1, pred = predict(randomforest_model, datos_validacion_limpio[,-15], type = "class") )
tabla_validacion_randomforest
## pred
## obs B1 C1
## B1 1755 151
## C1 147 1190
pred_caras_rf_validacion = tabla_validacion_randomforest[2,2]/(tabla_validacion_randomforest[2,2]+tabla_validacion_randomforest[1,2])
rec_caras_rf_validacion = tabla_validacion_randomforest[2,2]/(tabla_validacion_randomforest[2,2]+tabla_validacion_randomforest[2,1])
F_caras_rf_validacion=(2*pred_caras_rf_validacion*rec_caras_rf_validacion)/(pred_caras_rf_validacion+rec_caras_rf_validacion)
cat(c('F1 caras: ', F_caras_rf_validacion), '\n')
## F1 caras: 0.888722927557879
pred_baratas_rf_validacion = tabla_validacion_randomforest[1,1]/(tabla_validacion_randomforest[1,1]+tabla_validacion_randomforest[2,1])
rec_baratas_rf_validacion=tabla_validacion_randomforest[1,1]/(tabla_validacion_randomforest[1,1]+tabla_validacion_randomforest[1,2])
F_baratas_rf_validacion=(2*pred_baratas_rf_validacion*rec_baratas_rf_validacion)/(pred_baratas_rf_validacion+rec_baratas_rf_validacion)
cat(c('F1 baratas: ', F_baratas_rf_validacion), '\n')
## F1 baratas: 0.921743697478992
F_rf_validacion= (F_caras_rf_validacion+F_baratas_rf_validacion)/2
cat(c('F1 global: ', F_rf_validacion), '\n')
## F1 global: 0.905233312518435
accuracy_rf_validacion = (tabla_validacion_randomforest[1,1]+tabla_validacion_randomforest[2,2]) / (tabla_validacion_randomforest[1,1]+tabla_validacion_randomforest[1,2]+tabla_validacion_randomforest[2,1]+tabla_validacion_randomforest[2,2])
cat(c('Accuracy: ', accuracy_rf_validacion), '\n')
## Accuracy: 0.908109774899784
Una máquina de vectores de soporte (SVM) es un algoritmo de aprendizaje supervisado. Este algoritmo construye un hiperplano óptimo en forma de superficie de decisión, de modo que el margen de separación entre las dos clases en los datos sea lo más amplia posible. Los vectores de soporte hacen referencia a un pequeño subconjunto de las observaciones de entrenamiento que se utilizan como soporte para la ubicación óptima de la superficie de decisión.
La función svm de la librería e1071, toma como parámatros el kernel a emplear (en nuestro caso se usa un kernel lineal, linear), el coste (cost) para ajustar el número de vectores soporte y probility para indicar si el modelo debe permitir predicciones de probabilidad.
set.seed(1234)
modelo_svm <- e1071::svm(formula = price_categ1 ~.,
data = datos_train_limpio,
kernel = "linear",
probability =TRUE,
cost=100)
summary(modelo_svm)
##
## Call:
## svm(formula = price_categ1 ~ ., data = datos_train_limpio, kernel = "linear",
## probability = TRUE, cost = 100)
##
##
## Parameters:
## SVM-Type: C-classification
## SVM-Kernel: linear
## cost: 100
##
## Number of Support Vectors: 4688
##
## ( 2345 2343 )
##
##
## Number of Classes: 2
##
## Levels:
## B1 C1
Por último, se evalúa el resultado del modelo, obteniendo una F1-medida y accuracy de 0.87.
tabla_svm_train=table(obs = datos_train_limpio$price_categ1, pred = predict(modelo_svm,datos_train_limpio[,-15], type ="class"))
tabla_svm_train
## pred
## obs B1 C1
## B1 7837 897
## C1 1030 5355
pred_caras_svm_train=tabla_svm_train[2,2]/(tabla_svm_train[2,2]+tabla_svm_train[1,2])
rec_caras_svm_train=tabla_svm_train[2,2]/(tabla_svm_train[2,2]+tabla_svm_train[2,1])
F_caras_svm_train=(2*pred_caras_svm_train*rec_caras_svm_train)/(pred_caras_svm_train+rec_caras_svm_train)
cat(c('F1 caras: ', F_caras_svm_train), '\n')
## F1 caras: 0.84751127641054
pred_baratas_svm_train=tabla_svm_train[1,1]/(tabla_svm_train[1,1]+tabla_svm_train[2,1])
rec_baratas_svm_train=tabla_svm_train[1,1]/(tabla_svm_train[1,1]+tabla_svm_train[1,2])
F_baratas_svm_train=(2*pred_baratas_svm_train*rec_baratas_svm_train)/(pred_baratas_svm_train+rec_baratas_svm_train)
cat(c('F1 baratas: ', F_baratas_svm_train), '\n')
## F1 baratas: 0.890517584228169
F_svm_train= (F_caras_svm_train+F_baratas_svm_train)/2
cat(c('F1 global: ', F_svm_train), '\n')
## F1 global: 0.869014430319355
accuracy_svm_train = (tabla_svm_train[1,1]+tabla_svm_train[2,2]) / (tabla_svm_train[1,1]+tabla_svm_train[1,2]+tabla_svm_train[2,1]+tabla_svm_train[2,2])
cat(c('Accuracy: ', accuracy_svm_train), '\n')
## Accuracy: 0.872544480455057
tabla_svm_test=table(obs = datos_test_limpio$price_categ1, pred = predict(modelo_svm,datos_test_limpio[,-15], type ="class"))
tabla_svm_test
## pred
## obs B1 C1
## B1 1694 217
## C1 210 1120
pred_caras_svm_test=tabla_svm_test[2,2]/(tabla_svm_test[2,2]+tabla_svm_test[1,2])
rec_caras_svm_test=tabla_svm_test[2,2]/(tabla_svm_test[2,2]+tabla_svm_test[2,1])
F_caras_svm_test=(2*pred_caras_svm_test*rec_caras_svm_test)/(pred_caras_svm_test+rec_caras_svm_test)
cat(c('F1 caras: ', F_caras_svm_test), '\n')
## F1 caras: 0.83989501312336
pred_baratas_svm_test=tabla_svm_test[1,1]/(tabla_svm_test[1,1]+tabla_svm_test[2,1])
rec_baratas_svm_test=tabla_svm_test[1,1]/(tabla_svm_test[1,1]+tabla_svm_test[1,2])
F_baratas_svm_test=(2*pred_baratas_svm_test*rec_baratas_svm_test)/(pred_baratas_svm_test+rec_baratas_svm_test)
cat(c('F1 baratas: ', F_baratas_svm_test), '\n')
## F1 baratas: 0.888073394495413
F_svm_test= (F_caras_svm_test+F_baratas_svm_test)/2
cat(c('F1 global: ', F_svm_test), '\n')
## F1 global: 0.863984203809386
accuracy_svm_test = (tabla_svm_test[1,1]+tabla_svm_test[2,2]) / (tabla_svm_test[1,1]+tabla_svm_test[1,2]+tabla_svm_test[2,1]+tabla_svm_test[2,2])
cat(c('Accuracy: ', accuracy_svm_test), '\n')
## Accuracy: 0.868250539956803
tabla_svm_validacion=table(obs = datos_validacion_limpio$price_categ1, pred = predict(modelo_svm,datos_validacion_limpio[,-15], type ="class"))
tabla_svm_validacion
## pred
## obs B1 C1
## B1 1709 197
## C1 206 1131
pred_caras_svm_validacion=tabla_svm_validacion[2,2]/(tabla_svm_validacion[2,2]+tabla_svm_validacion[1,2])
rec_caras_svm_validacion=tabla_svm_validacion[2,2]/(tabla_svm_validacion[2,2]+tabla_svm_validacion[2,1])
F_caras_svm_validacion=(2*pred_caras_svm_validacion*rec_caras_svm_validacion)/(pred_caras_svm_validacion+rec_caras_svm_validacion)
cat(c('F1 caras: ', F_caras_svm_validacion), '\n')
## F1 caras: 0.848780487804878
pred_baratas_svm_validacion=tabla_svm_validacion[1,1]/(tabla_svm_validacion[1,1]+tabla_svm_validacion[2,1])
rec_baratas_svm_validacion=tabla_svm_validacion[1,1]/(tabla_svm_validacion[1,1]+tabla_svm_validacion[1,2])
F_baratas_svm_validacion=(2*pred_baratas_svm_validacion*rec_baratas_svm_validacion)/(pred_baratas_svm_validacion+rec_baratas_svm_validacion)
cat(c('F1 baratas: ', F_baratas_svm_validacion), '\n')
## F1 baratas: 0.894530227689087
F_svm_validacion= (F_caras_svm_validacion+F_baratas_svm_validacion)/2
cat(c('F1 global: ', F_svm_validacion), '\n')
## F1 global: 0.871655357746982
accuracy_svm_validacion = (tabla_svm_validacion[1,1]+tabla_svm_validacion[2,2]) / (tabla_svm_validacion[1,1]+tabla_svm_validacion[1,2]+tabla_svm_validacion[2,1]+tabla_svm_validacion[2,2])
cat(c('Accuracy: ', accuracy_svm_validacion), '\n')
## Accuracy: 0.875732346592661
svm_tune <- tune(svm, price_categ1 ~., data = datos_train_limpio, ranges = list(cost = 2^(2:4)), tunecontrol = tune.control(sampling = "fix"))
summary(svm_tune)
##
## Parameter tuning of 'svm':
##
## - sampling method: fixed training/validation set
##
## - best parameters:
## cost
## 16
##
## - best performance: 0.110119
##
## - Detailed performance results:
## cost error dispersion
## 1 4 0.1142857 NA
## 2 8 0.1125000 NA
## 3 16 0.1101190 NA
plot(svm_tune)
Pese a que el ajuste de hiperparámetros indica que el valor óptimo de cost es 8, repetimos los cálculos y la F1-global mejora muy poco (aprox. 0.0004). Pasamos de una F1 de 0.8686 a una F1 de 0.8690.
datos_train_gam <- datos_train_limpio
datos_train_gam$price <- datos_train$price
model_gam <- gam(price ~ s(bedrooms, bs = "cr") + bathrooms_group + s(log_sqft_living, bs = "cr") +
s(log_lot, bs = "cr") + sqft_basement_cat + waterfront + view + s(lat, bs = "cr") +
s(long, bs = "cr") + zona + yr_renovated_catg, data = datos_train_gam)
summary(model_gam)
##
## Family: gaussian
## Link function: identity
##
## Formula:
## price ~ s(bedrooms, bs = "cr") + bathrooms_group + s(log_sqft_living,
## bs = "cr") + s(log_lot, bs = "cr") + sqft_basement_cat +
## waterfront + view + s(lat, bs = "cr") + s(long, bs = "cr") +
## zona + yr_renovated_catg
##
## Parametric coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 460094 3772 121.978 < 2e-16 ***
## bathrooms_group1 35433 4091 8.662 < 2e-16 ***
## sqft_basement_cat1 -33249 3315 -10.029 < 2e-16 ***
## waterfront1 523178 20514 25.503 < 2e-16 ***
## view1 90905 11827 7.686 1.61e-14 ***
## view2 82058 7077 11.595 < 2e-16 ***
## view3 155689 9712 16.031 < 2e-16 ***
## view4 287662 14672 19.606 < 2e-16 ***
## zona2 126977 5000 25.398 < 2e-16 ***
## yr_renovated_catg1 58719 7070 8.306 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Approximate significance of smooth terms:
## edf Ref.df F p-value
## s(bedrooms) 8.739 8.957 19.8 <2e-16 ***
## s(log_sqft_living) 8.911 8.997 1509.6 <2e-16 ***
## s(log_lot) 8.339 8.802 37.3 <2e-16 ***
## s(lat) 8.872 8.995 280.6 <2e-16 ***
## s(long) 8.935 8.999 171.2 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## R-sq.(adj) = 0.781 Deviance explained = 78.2%
## GCV = 3.0207e+10 Scale est. = 3.0099e+10 n = 15119
plot(model_gam, ylab = "price")
par(mfrow=c(3,3))
visreg(model_gam)
datos_test_limpio$price <- datos_test$price
datos_test_limpio$price_predict = predict(model_gam, datos_test_limpio)
datos_test_limpio$precio_diff <- abs(datos_test_limpio$price - datos_test_limpio$price_predict)
dif_media_test <- mean(datos_test_limpio$precio_diff)
dif_media_test
## [1] 108157.9
datos_validacion_limpio$price <- datos_validacion$price
datos_validacion_limpio$price_predict = predict(model_gam, datos_validacion_limpio)
datos_validacion_limpio$precio_diff <- abs(datos_validacion_limpio$price - datos_validacion_limpio$price_predict)
dif_media_test <- mean(datos_validacion_limpio$precio_diff)
dif_media_test
## [1] 105924.8
Una vez que se han entrenado y optimizado distintos modelos, se tiene que identificar cuál de ellos consigue mejores resultados para el problema en cuestión, en este caso, predecir si una casa es barata o cara. Con los datos disponibles, existen dos formas de comparar los modelos. Si bien las dos no tienen por qué dar los mismos resultados, son complementarias a la hora de tomar una decisión final.
models_cross = data.frame(
"modelo"= c('GLM','knn','Decision_Tree','Random_Forest','SVM'),
"F_Medida_train" = c(F_reglog_train,F_knn_train,F_dt_train,F_rf_train,F_svm_train))
plot(x = models_cross$modelo, y= models_cross$F_Medida_train,fill=models_cross$modelo)
ggplot(data=models_cross, aes(x=modelo, y=F_Medida_train, fill=modelo)) +
geom_bar(stat="identity", position="dodge")
# REGRESIÓN LOGÍSTICA
predictions_glm <- predict(model_glm, newdata = datos_train_rl, type = "response")
pred_glm <- prediction(predictions_glm, datos_train_rl$price_categ1)
perf_glm <- performance(pred_glm,"tpr","fpr")
# KNN
# model_knn=knn.cv(scale(datos_train_numeric),cl=as.factor(datos_train_limpio$price_categ1),k=17, prob = TRUE)
predictions_Knn <- knn(scale(datos_train_numeric), scale(datos_train_numeric), cl=datos_train_limpio$price_categ1, k=17, prob = TRUE)
pred_knn <- prediction(attr(predictions_Knn,"prob"), datos_train_limpio$price_categ1)
perf_knn <- performance(pred_knn,"tpr","fpr")
# ARBOL DE DECISION
predictions_tree <- predict(decisiontree_model, newdata = datos_decision_tree, type = "prob")
pred_tree = prediction(predictions_tree[,2], datos_decision_tree$price_categ1)
pref_tree = performance(pred_tree, "tpr", "fpr")
# RANDOM FOREST
predictions_rf <- predict(randomforest_model, new_data=datos_train_limpio, type = "prob")
pred_rf <- prediction(predictions_rf[,2],datos_train_limpio$price_categ1)
perf_rf <- performance(pred_rf,"tpr","fpr")
# SVM
predictions_svm <- predict(modelo_svm, newdata=datos_train_limpio, probability = TRUE)
prob_svm<-attr(predictions_svm,"probabilities")
pred_svm <- prediction(prob_svm[,2], datos_train_limpio$price_categ)
perf_svm <- performance(pred_svm,"tpr","fpr")
plot(perf_glm,col="blue")
plot(pref_tree, col="red",add = TRUE)
plot(perf_rf,col="green", add = TRUE)
plot(perf_svm, col="yellow",add = TRUE)
plot(perf_knn, col="orange",add = TRUE)
legend(x="right", legend=c("GLM","DT","RF","SVM","KNN"), fill=c("blue","red","green","yellow","orange"), cex=0.8)